/*
 * Copyright 2008 FBK (http://www.fbk.eu/)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fbk.it.hlt.jlsi;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.fbk.it.hlt.jlsi.data.DenseTextMatrixFileReader;
import org.fbk.it.hlt.jlsi.data.DenseTextVectorFileReader;

import cern.colt.list.DoubleArrayList;
import cern.colt.list.IntArrayList;
import cern.colt.matrix.DoubleMatrix1D;
import cern.colt.matrix.DoubleMatrix2D;
import cern.colt.matrix.impl.DenseDoubleMatrix1D;
import cern.colt.matrix.impl.SparseDoubleMatrix1D;

/**
 * TO DO
 * 
 * @author Claudio Giuliano
 * @version %I%, %G%
 * @since 1.0
 * @see DomainMapping
 */
public class DomainMapping {
	/**
	 * Define a static logger variable so that it references the Logger instance
	 * named <code>DomainMapping</code>.
	 */
	static Logger logger = Logger.getLogger(DomainMapping.class.getName());

	/**
	 * to do.
	 */
	private DoubleMatrix2D Uk;

	/**
	 * to do.
	 */
	private DoubleMatrix1D Sk;

	/**
	 * to do.
	 */
	private DoubleMatrix1D Iidf;

	//
	private TermIndex termIndex;

	//
	private DocumentIndex documentIndex;

	// Number of terms
	private int N;

	// Number of documents
	// private int l;

	// k reduce dimensionality
	private int k;

	/**
	 * Constructs a <code>DomainMapping</code> object.
	 */
	public DomainMapping(File UtFile, File SFile, File rowFile, File colFile,
			File dfFile) throws IOException {
		termIndex = TermIndex.getTermIndex();
		termIndex.read(new FileReader(rowFile));

		documentIndex = DocumentIndex.getDocumentIndex();
		documentIndex.read(new FileReader(colFile));

		// number of documents
		int l = documentIndex.documentSet().size();

		Vocabulary voc = new Vocabulary();
		voc.read(new FileReader(dfFile));
		createIdf(voc, l);

		DenseTextVectorFileReader SVectorFileReader = new DenseTextVectorFileReader(
				SFile);
		Sk = (DoubleMatrix1D) SVectorFileReader.read();

		DenseTextMatrixFileReader matrixFileReader = new DenseTextMatrixFileReader(
				UtFile);
		Uk = (DoubleMatrix2D) matrixFileReader.read();

		N = Uk.rows();
		k = Sk.size();

		// System.out.println(Uk);

		rescale();
		normalize();
		idf();

		// termByTerm(voc);
	} // end constructor

	//
	private void createIdf(Vocabulary voc, int l) {
		Iidf = new DenseDoubleMatrix1D(voc.entrySet().size());
		// logger.debug("Iidf.size: " + Iidf.size());
		// iterates over the types
		Iterator it = voc.entrySet().iterator();
		while (it.hasNext()) {
			Map.Entry me = (Map.Entry) it.next();
			String term = (String) me.getKey();

			Vocabulary.TermFrequency tf = (Vocabulary.TermFrequency) me
					.getValue();
			int index = termIndex.getIndex(term);

			// log ( l / df)
			Iidf.setQuick(index, Math.log((double) l / tf.get()));
		} // end while
	} // end createIdf

	//
	private void rescale() {
		logger.debug("rescale: Uk[" + Uk.rows() + " X " + Uk.columns()
				+ "] * Sk[" + Uk.columns() + " X " + Uk.columns() + "]");

		// Uk.col = l
		for (int i = 0; i < Uk.columns(); i++) {
			DoubleMatrix1D col = Uk.viewColumn(i);
			// logger.info(col);

			for (int j = 0; j < col.size(); j++) {
				// logger.info(j + " " + col.getQuick(j) + " * " +
				// Sk.getQuick(i));
				col.setQuick(j, col.getQuick(j) * Math.sqrt(Sk.getQuick(i)));
			} // end for j
		} // end for i
	} // end rescale

	//
	private void normalize() {
		logger.debug("normalize: Iidf[" + Iidf.size() + " X " + Iidf.size()
				+ "] * Uk[" + Uk.rows() + " X " + Uk.columns() + "]");

		// Uk.rows = N
		for (int i = 0; i < Uk.rows(); i++) {
			DoubleMatrix1D row = Uk.viewRow(i);

			double sum = 0;
			for (int j = 0; j < row.size(); j++)
				sum += Math.pow(row.getQuick(j), 2);

			// normalization
			sum = Math.sqrt(sum);
			for (int j = 0; j < row.size(); j++) {
				// logger.debug(row.getQuick(j) + " / " + sum + " = " +
				// (row.getQuick(j) / sum));
				row.setQuick(j, row.getQuick(j) / sum);
			}
		} // end for i

	} // end normalize

	//
	private void idf() {
		logger.debug("idf: Iidf[" + Iidf.size() + " X " + Iidf.size()
				+ "] * Uk[" + Uk.rows() + " X " + Uk.columns() + "]");

		// Uk.rows = N
		for (int i = 0; i < Uk.rows(); i++) {
			DoubleMatrix1D row = Uk.viewRow(i);

			// I^idf X (Uk * Sk)
			for (int j = 0; j < row.size(); j++)
				row.setQuick(j, row.getQuick(j) * Iidf.getQuick(i));

		} // end for i

	} // end normalize

	/**
	 *
	 */
	public DoubleMatrix1D getVector(String term) {
		int i = termIndex.getIndex(term);
		// logger.info(term + " " + i);

		if (i == -1)
			return null;

		return Uk.viewRow(i);
	} // end getVector

	/**
	 * Returns a document in the VSM.
	 */
	public DoubleMatrix1D createDocument(String[] terms) {
		// N = Uk.rows();
		DoubleMatrix1D doc = new SparseDoubleMatrix1D(Uk.rows());

		for (int i = 0; i < terms.length; i++) {
			int index = termIndex.getIndex(terms[i]);
			logger.debug(terms[i] + " ==> " + index);
			doc.setQuick(index, 1);
		} // end for

		return doc;
	} // end createDocument

	/**
	 * Returns a document in the VSM.
	 */
	public DoubleMatrix1D createDocument(Set terms) {
		// N = Uk.rows();
		DoubleMatrix1D doc = new SparseDoubleMatrix1D(Uk.rows());

		Iterator it = terms.iterator();
		while (it.hasNext()) {
			String t = (String) it.next();
			int index = termIndex.getIndex(t);
			logger.debug(t + " ==> " + index);
			doc.setQuick(index, 1);
			// doc.setQuick(index, doc.getQuick() + 1); // tf
		} // end for

		return doc;
	} // end createDocument

	/**
	 * Returns a document in the domain VSM.
	 */
	public DoubleMatrix1D createPseudoDocument(Set terms) {
		return createPseudoDocument(createDocument(terms));
	} // end createPseudoDocument

	/**
	 * Returns a document in the domain VSM.
	 */
	public DoubleMatrix1D createPseudoDocument(DoubleMatrix1D doc) {
		// N = Uk.rows();
		DoubleMatrix1D pdoc = new DenseDoubleMatrix1D(k);

		logger.debug("Uk.size " + Uk.rows() + " X " + Uk.columns());
		logger.debug("doc.size " + doc.size());
		logger.debug("pdoc.size " + pdoc.size());

		for (int i = 0; i < k; i++) {
			DoubleMatrix1D di = Uk.viewColumn(i);

			double dot = di.zDotProduct(doc);
			logger.debug(doc + " * " + di + " = " + dot);

			pdoc.setQuick(i, dot);
		} // end for i

		return pdoc;
	} // end createPseudoDocument

	//
	public static void main(String[] args) throws Exception {
		String logConfig = System.getProperty("log-config");
		if (logConfig == null)
			logConfig = "log-config.txt";

		long begin = System.currentTimeMillis();

		PropertyConfigurator.configure(logConfig);

		if (args.length < 1) {
			System.out.println(getHelp());
			System.exit(1);
		}

		File Ut = new File(args[0] + "-Ut");
		File Sk = new File(args[0] + "-S");
		File r = new File(args[0] + "-row");
		File c = new File(args[0] + "-col");
		File df = new File(args[0] + "-df");

		String[] terms = new String[args.length - 1];

		for (int i = 0; i < terms.length; i++)
			terms[i] = args[i + 1];

		DomainMapping ts = new DomainMapping(Ut, Sk, r, c, df);
		DoubleMatrix1D doc = ts.createDocument(terms);

		IntArrayList indexList = new IntArrayList();
		DoubleArrayList valueList = new DoubleArrayList();
		doc.getNonZeros(indexList, valueList);

		System.out.println(doc);
		System.out.println(indexList);
		System.out.println(valueList);

		DoubleMatrix1D pdoc = ts.createPseudoDocument(doc);

		DoubleArrayList pValueList = new DoubleArrayList();
		indexList = new IntArrayList();
		valueList = new DoubleArrayList();
		pdoc.getNonZeros(indexList, valueList);

		System.out.println(pdoc);
		System.out.println(indexList);
		System.out.println(valueList);

		long end = System.currentTimeMillis();
		System.out.println("term similarity calculated in " + (end - begin)
				+ " ms");
	} // end main

	/**
	 * Returns a command-line help.
	 * 
	 * return a command-line help.
	 */
	private static String getHelp() {
		StringBuffer sb = new StringBuffer();

		// License
		sb.append(License.get());

		// Usage
		sb
				.append("Usage: java -mx1024M org.fbk.it.hlt.jlsi.DomainMapping input (term)+\n\n");

		// Arguments
		sb.append("Arguments:\n");
		sb
				.append("\tinput\t\t-> root of files from which to read the term-by-document matrix (in sparse binary format), row index, col index and term document frequency\n");
		sb.append("\tterm\t\t-> input term\n");

		// Arguments
		// sb.append("Arguments:\n");

		return sb.toString();
	} // end getHelp

} // end class DomainMapping